open Ast

let stab = ref []

let print_stab  s = print_endline "stab = "; 
	List.iter (fun v -> print_string (Ast.string_of_var v)) s

let rec term = function
	Integer i -> Ast.Int
	| String(s) -> Ast.Str
	| Array(a) -> Ast.Arr
	(* check the stab for a var and get its explicit or dynamic type  *)
	| Id(i) -> let v =  try (List.find i !stab) with Not_Found -> 
					raise (Failure ("Undeclared variable " ^ i))
			in if (v.varType <> Ast.Any) then v.varType
			(*else(if (v.dType <> Ast.Any) then v.dType *)
	| Null -> Ast.Any
	
let rec expr = function
	Term (a) -> term a
	(** bound to cause problems *)
	| Call (fname, args) -> Null
	| Range (id, a1, a2) -> let t1 = term a1 and t2 = term a2 and i = term id in
		if ((t1 = Ast.Int) && (t2 = Ast.Int) && (i <> Ast.Int)) then i else
		raise (Failure ("Invalid sub-string/sub-array expression"))
	| Binop(e, o, a) -> let op1 = expr e and op2 = term a in
			let eq = if (op1 = op2) then op2 else raise (Failure
			("A binary operation was attempted on incompatible types"))
			in let ints i = if (i = Ast.Int) then i else raise (Failure
			("An arithmetic/boolean operation was attempted on non-integers"))
			in (match o with
		Vat -> let m = expr e in  if (m <> Ast.Int) then 
			  (let i = term a in  if (i = Ast.Int) then i else raise (Failure
			    ("Invalid string/array reference index supplied")))
			    else raise (Failure	("Invalid string/array")))
		| Lt -> ints eq
		| Leq -> ints eq
		| Gt -> ints eq
		| Geq -> ints eq
		| And -> ints eq
		| Or -> ints eq
		| Sub ->	ints eq
		| _ -> eq
	| Unop(v, o) -> (match o with
	  	Len -> let t = term v in if(t <> Ast.Int) then t  else raise (Failure
			("Cannot compute the length of this argument"))
		| Not -> let e = expr v in if(e = Ast.Int) then e  else raise (Failure
			("An boolean operation was attempted on non-integers"))
		| In -> let t = term v in if(t <> Ast.Arr) then t  else raise (Failure
			("Invalid input source")))
		
let rec stmt = function
    Block(stmts) -> List.map stmt stmts
  | Return(e) -> expr e
  | If(e, s) -> let e1 = expr e in if(e = Ast.Int) then stmt s  else raise (Failure
			("Invalid predicate in If statement"))
  | While(e, s) -> let e1 = expr e in if(e = Ast.Int) then stmt s  else raise (Failure
			("Invalid predicate in While statement"))		  
(**  | Foreach(id, a, s) -> Foreach(id, term a, stmt s)*)
  | SetAIndex(id, i, e) -> let t1 = term id and t2= term i and e1 = expre e in
  	if((t1 = Ast.Arr) && (t2 = Ast.Int) && (e1 <> Ast.Int)) then e1
  	else raise (Failure ("Invalid array index assignment"))  
  | Output(t, e) -> Ast.Any
  (* set the vartype during assignment if it was not initialized *)
  (* if the var has an explicit type, make sure the expr has that type *)
  | Assign(v, e) -> let eT = expr e in if (v.varType = Ast.Any) then (let     v.dType = eT)
  			else(if (v.varType <> eT) then raise (Failure
  			("Invalid assignment: " ^ v.varName ^ " of " ^ v.varType ^ " to " ^ eT))) 
  			in (stab := v::!stab; Ast.Any


let type_check func = List.map (fun -> stab:=[]; stmt) func.body;
		
	(*{fname = func.fname;
	 params = func.params;
	 body = 
	 locals = func.locals} *)

let check (others, functions) = 
	let valid_typed_funs = List.map type_check functions in
	(others, valid_typed_funs);

